#include <stdlib.h>
#include <stdio.h>	
#include <string.h>
#include "util.h"

int bool_comp_int (int a, int b, const char * c){
  int res = 0;
  switch(c[0]){
  case '<':
    if(c[1]=='=') {
      if(a<=b) {res=1;}}
    else if(a<b) res=1;
    break;
  case '>':
    if(c[1]=='=') {
      if(a>=b) {res=1;}}
    else if(a>b) res=1;
    break;
  case '=':
    if(a==b) res=1;
    break;
  case '!':
    if(a!=b) res=1;
    break;
  case 'a':
    if(a&&b) res=1;
    break;
  case 'o':
    if(a||b) res=1;
    break;
  default:
    break;
  }
  return res;
}

int bool_comp_float (float a, float b, const char * c){
  int res = 0;
  switch(c[0]){
  case '<':
    if(c[1]=='=') {
      if(a<=b) {res=1;}}
    else if(a<b) res=1;
    break;
  case '>':
    if(c[1]=='=') {
      if(a>=b) {res=1;}}
    else if(a>b) res=1;
    break;
  case '=':
    if(a==b) res=1;
    break;
  case '!':
    if(a!=b) res=1;
    break;
  case 'a':
    if(a&&b) res=1;
    break;
  case 'o':
    if(a||b) res=1;
    break;
  default:
    break;
  }
  return res;
}

struct var * init_id (struct table * t, const char * d1,int * r) {
  struct var * v=NULL;
  if(d1[0]=='$') {
    if((v = exists_glob(t,d1))==NULL) {
      v = creer_var(d1,*r);
      ajouter_glob(t,v);
      (*r)++;
    }
  }
  else{
    if((v = exists_loc(t,d1))==NULL) {
      v = creer_var(d1,*r);
      ajouter_loc(t,v);
      (*r)++;
    }
  }
  return v;
}

struct var * charger_var (const struct var * v, int *r){
  struct var * res=NULL;
  char tmp[MAX_LINE_SIZE];
  switch(v->type){

  case TYPE_INT:
    if(v->calculable == CALCULABLE) {
      res = creer_var_int(NULL,v->u.i,*(r),CALCULABLE);
    }
    else {
      res = creer_var_int(NULL,0,*(r),NON_CALCULABLE);
    }
    sprintf(tmp,"%%r%d = load i32* %%r%d\n",*(r),v->r);
    concatener(&(res->code),tmp);
    break;

  case TYPE_BOOLEAN:
    if(v->calculable == CALCULABLE) {
      res = creer_var_int(NULL,v->u.i,*(r),CALCULABLE);
    }
    else {
      res = creer_var_int(NULL,0,*(r),NON_CALCULABLE);
    }
    sprintf(tmp,"%%r%d = load i1* %%r%d\n",*(r),v->r);
    concatener(&(res->code),tmp);
    break;

  case TYPE_FLOAT:
    if(v->calculable == CALCULABLE) {
      res = creer_var_float(NULL,v->u.f,*(r),CALCULABLE);
    }
    else {
      res = creer_var_float(NULL,0,*(r),NON_CALCULABLE);
    }
    sprintf(tmp,"%%r%d = load float* %%r%d\n",*(r),v->r);
    concatener(&(res->code),tmp);
    break;
  case TYPE_STRING:
    // TO DO
    break;
  default:
    break;
  }
  (*r)++;
  return res;
}

struct sdc * affectation (struct var * d1, const struct var * d2){
  struct sdc * v = init_sdc();
  char tmp[MAX_LINE_SIZE];
  if(d1->type!=TYPE_UNDEF) { // Le type de lhs est défini
    if(d1->type!=d2->type) { // Si les types ne sont pas les mêmes
      yyerror("Erreur de type\n");
      exit(EXIT_FAILURE);
    }
    else{ // Les types sont les mêmes
      
      if(d2->type==TYPE_INT){
	if(d2->calculable==CALCULABLE) {
	  d1->u.i = d2->u.i;
	  d1->calculable = CALCULABLE;
	  sprintf(tmp,"store i32 %d, i32* %%r%d\n",d2->u.i,d1->r);
	  concatener(&(v->code),tmp);
	}
	else {
	  concatener(&(v->code),d2->code);
	  sprintf(tmp,"store i32 %%r%d, i32* %%r%d\n",d2->r,d1->r);
	  concatener(&(v->code),tmp);
	}
      }
      else if(d2->type==TYPE_FLOAT){
	if(d2->calculable==CALCULABLE) {
	  d1->u.f = d2->u.f;
	  d1->calculable = CALCULABLE;
	  sprintf(tmp,"store float %e, float* %%r%d\n",d2->u.f,d1->r);
	  concatener(&(v->code),tmp);
	}
	else {
	  concatener(&(v->code),d2->code);
	  sprintf(tmp,"store float %%r%d, float* %%r%d\n",d2->r,d1->r);
	  concatener(&(v->code),tmp);
	}
      }
      else if(d2->type==TYPE_STRING){
	// TO DO
      }
    }
  }
  else{ // Le type de lhs n'est pas défini, on fait une déclaration
    d1->type = d2->type;
    if(d2->type==TYPE_INT){
      sprintf(tmp,"%%r%d = alloca i32\n",d1->r);
      concatener(&(v->code),tmp);
      if(d2->calculable==CALCULABLE) {
	d1->u.i = d2->u.i;
	d1->calculable = CALCULABLE;
	sprintf(tmp,"store i32 %d, i32* %%r%d\n",d2->u.i,d1->r);
	concatener(&(v->code),tmp);
      }
      else {
	d1->calculable = NON_CALCULABLE;
	concatener(&(v->code),d2->code);
	sprintf(tmp,"store i32 %%r%d, i32* %%r%d\n",d2->r,d1->r);
	concatener(&(v->code),tmp);
      }
    }
    else if(d2->type==TYPE_FLOAT){
      sprintf(tmp,"%%r%d = alloca float\n",d1->r);
      concatener(&(v->code),tmp);
      if(d2->calculable==CALCULABLE) {
	d1->u.f = d2->u.f;
	d1->calculable = CALCULABLE;
	sprintf(tmp,"store float %e, float* %%r%d\n",d2->u.f,d1->r);
	concatener(&(v->code),tmp);
      }
      else {
	d1->calculable = NON_CALCULABLE;
	concatener(&(v->code),d2->code);
	sprintf(tmp,"store float %%r%d, float* %%r%d\n",d2->r,d1->r);
	concatener(&(v->code),tmp);
      }
    }
    else if(d2->type==TYPE_BOOLEAN){
      sprintf(tmp,"%%r%d = alloca i1\n",d1->r);
      concatener(&(v->code),tmp);
      if(d2->calculable==CALCULABLE) {
	d1->u.i = d2->u.i;
	d1->calculable = CALCULABLE;
	sprintf(tmp,"store i1 %d, i1* %%r%d\n",d2->u.i,d1->r);
	concatener(&(v->code),tmp);
      }
      else {
	d1->calculable = NON_CALCULABLE;
	concatener(&(v->code),d2->code);
	sprintf(tmp,"store i1 %%r%d, i1* %%r%d\n",d2->r,d1->r);
	concatener(&(v->code),tmp);
      }
    }
    else if(d2->type==TYPE_STRING){
      // TO DO
    }
  }
  return v;
}

struct var * expr (const struct var * d1,  // adresse de l'expression à gauche de l'opérateur
		   const struct var * d2,  // adresse de l'expression à droite de l'opérateur
		   const char * int_op,    // instruction llvm corespondante (opération sur entiers)
		   const char * flt_op,    // instruction llvm corresondante (opération sur flottants)
		   int int_val,            // valeur à stocker (opération sur entiers)
		   float flt_val,          // valeur à stocker (opération sur flottants)
		   int bool_flt_val,       // valeur booléenne à stocker (si comparaison entre flottants)
		   int *r,                 // registre courant
		   char bool_comp)         // indique si l'expression est booléenne
{
  struct var * v = NULL;
  char tmp[MAX_LINE_SIZE];
  char * code = (char*)malloc(1);
  code[0]='\0';
  if(d1->type != d2->type){
    yyerror("Erreur de types\n");
    exit(EXIT_FAILURE);
  }
  if(d1->calculable==CALCULABLE && d2->calculable==CALCULABLE) {
    switch(d1->type){
    case TYPE_INT:
      v = creer_var_int(NULL,int_val,*(r),CALCULABLE);
      break;
    case TYPE_FLOAT:
      if(bool_comp){
	v = creer_var_int(NULL,bool_flt_val,*(r),CALCULABLE);
      }
      else{
	v = creer_var_float(NULL,flt_val,*(r),CALCULABLE);
      }
      break;
    default:
      break;
    }
    (*r)++;
  }
  else{
    struct var * var_tmp = NULL;
    struct var * var_tmp2 = NULL;
    switch(d1->type){
    case TYPE_INT:
      v = creer_var_int(NULL,0,*(r),NON_CALCULABLE);
      sprintf(tmp,"%%r%d = %s i32 ",*(r),int_op);
      break;
    case TYPE_BOOLEAN:
      v = creer_var_int(NULL,0,*(r),NON_CALCULABLE);
      sprintf(tmp,"%%r%d = %s i1 ",*(r),int_op);
      break;
    case TYPE_FLOAT:
      v = creer_var_float(NULL,0,*(r),NON_CALCULABLE);
      sprintf(tmp,"%%r%d = %s float ",*(r),flt_op);
      break;
    default:
      break;
    }
    concatener(&code,tmp);
    (*r)++;
    if(d1->calculable == CALCULABLE) { // $1 est calculable, $2 ne l'est pas
      if(d2->nom != NULL){             // $2 est un symbole : il faut faire un load
	var_tmp = charger_var(d2,r);   // load
	concatener(&(v->code),var_tmp->code);
	switch(d1->type){
	case TYPE_INT:
	  sprintf(tmp,"%d, %%r%d\n",d1->u.i,var_tmp->r);
	  break;
	case TYPE_BOOLEAN:
	  sprintf(tmp,"%d, %%r%d\n",d1->u.i,var_tmp->r);
	  break;
	case TYPE_FLOAT:
	  sprintf(tmp,"%e, %%r%d\n",d1->u.f,var_tmp->r);
	  break;
	default:
	  break;
	}
	concatener(&code,tmp);
      }
      else {  // $3 n'est pas un symbole, pas de load
	concatener(&(v->code),d1->code); // affichage des calculs de $3
	switch(d1->type){
	case TYPE_INT:
	  sprintf(tmp,"%d, %%r%d\n",d1->u.i,d2->r);
	  break;
	case TYPE_BOOLEAN:
	  sprintf(tmp,"%d, %%r%d\n",d1->u.i,d2->r);
	  break;
	case TYPE_FLOAT:
	  sprintf(tmp,"%e, %%r%d\n",d1->u.f,d2->r);
	  break;
	default:
	  break;
	}
	concatener(&code,tmp);
      }
    }
    else if(d2->calculable == CALCULABLE){ // $1 n'est pas calculable , $2 si
      if(d1->nom != NULL){                 // $1 est un symbole, il faut faire un load
	var_tmp = charger_var(d1,r);       // load
	concatener(&(v->code),var_tmp->code);
	switch(d1->type){
	case TYPE_INT:
	  sprintf(tmp,"%%r%d, %d\n",var_tmp->r,d2->u.i);
	  break;
	case TYPE_BOOLEAN:
	  sprintf(tmp,"%%r%d, %d\n",var_tmp->r,d2->u.i);
	  break;
	case TYPE_FLOAT:
	  sprintf(tmp,"%%r%d, %e\n",var_tmp->r,d2->u.f);
	  break;
	default:
	  break;
	}
	concatener(&code,tmp);
      }
      else {   // $1 n'est pas un symbole, pas de load
	concatener(&(v->code),d1->code); // calculs de $1
	switch(d1->type){
	case TYPE_INT:
	  sprintf(tmp,"%%r%d, %d\n",d1->r,d2->u.i);	  
	  break;
	case TYPE_BOOLEAN:
	  sprintf(tmp,"%%r%d, %d\n",d1->r,d2->u.i);
	  break;
	case TYPE_FLOAT:
	  sprintf(tmp,"%%r%d, %e\n",d1->r,d2->u.f);
	  break;
	default:
	  break;
	}
	concatener(&code,tmp);
      }
    }
    else{          // $1 et $2 ne sont pas calculables
	if(d1->nom != NULL){   // $1 est un symbole, il faut le load
	  var_tmp = charger_var(d1,r);      // load de $1
	  concatener(&(v->code),var_tmp->code);
	  sprintf(tmp,"%%r%d",var_tmp->r);
	}
	else {      //$1 n'est pas un symbole
	  concatener(&(v->code),d1->code);  // On affiche le code de $1
	  sprintf(tmp,"%%r%d",d1->r);
	}
	concatener(&code,tmp);	
	if(d2->nom != NULL){    // $2 est un symbole, il faut le load
	  var_tmp2 = charger_var(d2,r);  // load de $2
	  concatener(&(v->code),var_tmp2->code);
	  sprintf(tmp,", %%r%d\n",var_tmp2->r);
	}
	else{     // $2 n'est pas un symbole
	  concatener(&(v->code),d2->code);  // On affiche le code de $2
	  sprintf(tmp,"%%r%d\n",d2->r);
	}
	concatener(&code,tmp);	
    }
  }
  concatener(&(v->code),code);
  free(code);
  return v;
}

struct table * init_table (void) {
  struct table * t = (struct table *)malloc(sizeof(struct table));
  t->glob = NULL;
  t->loc = NULL;
  t->var_tmp = NULL;
  t->st = NULL;
  return t;
}

struct sdc * init_sdc(void){
  struct sdc * s = (struct sdc*)malloc(sizeof(struct sdc));
  s->code = (char*)malloc(1);
  s->code[0]='\0';
  s->prec = NULL;
  s->type_else = TYPE_NON_ELSE;
  return s;
}

struct var * creer_var(const char * nom,int r){
  struct var * res = (struct var *)malloc(sizeof(struct var));
  res->nom = strdup(nom);
  res->r = r;
  res->type = TYPE_UNDEF;
  res->prec = NULL;
  res->calculable=CALCULABLE;
  res->code = (char*)malloc(1);
  res->code[0]='\0';
  return res;
}

struct var * creer_var_void(void){
  struct var * res = (struct var *)malloc(sizeof(struct var));
  res->nom = NULL;
  res->r = NO_REG;
  res->type = TYPE_VOID;
  res->prec = NULL;
  res->calculable=NON_CALCULABLE;
  res->code = (char*)malloc(1);
  res->code[0]='\0';
  return res;
}

struct var * creer_var_int(const char * nom,int i,int r,char c){
  struct var * res = (struct var *)malloc(sizeof(struct var));
  if(nom!=NULL)
    res->nom = strdup(nom);
  else
    res->nom = NULL;
  res->u.i = i;
  res->r = r;
  res->type = TYPE_INT;
  res->prec = NULL;
  res->calculable=c;
  res->code = (char*)malloc(1);
  res->code[0]='\0';
  return res;
}

struct var * creer_var_float(const char * nom,float f,int r,char c){
  struct var * res = (struct var *)malloc(sizeof(struct var));
  if(nom!=NULL)
    res->nom = strdup(nom);
  else
    res->nom = NULL;
  res->u.f = f;
  res->r = r;
  res->type = TYPE_FLOAT;
  res->prec = NULL;
  res->calculable = c;
  res->code = (char*)malloc(1);
  res->code[0]='\0';
  return res;
}

struct var * creer_var_string(const char * nom, const char * s, int r){
  struct var * res = (struct var *)malloc(sizeof(struct var));
  if(nom!=NULL)
    res->nom = strdup(nom);
  else
    res->nom = NULL;
  res->u.s = strdup(s);
  res->r = r;
  res->type = TYPE_STRING;
  res->prec = NULL;
  res->code = (char*)malloc(1);
  res->code[0]='\0';
  return res;
}

struct var * creer_sentinelle(void){
  struct var * res = (struct var *)malloc(sizeof(struct var));
  res->nom = NULL;
  res->r = NO_REG;
  res->type = TYPE_SENTINELLE;
  res->prec = NULL;
  res->code = (char*)malloc(1);
  res->code[0]='\0';
  return res;
}

void ajouter_glob (struct table * t, struct var * v){
  v->prec = t->glob;
  t->glob = v;
}

void ajouter_loc (struct table * t, struct var * v){
  v->prec = t->loc;
  t->loc = v;
}

void ajouter_tmp (struct table * t, struct var * v){
  v->prec = t->var_tmp;
  t->var_tmp = v;
}

void ajouter_sdc (struct table * t, struct sdc * s){
  s->prec = t->st;
  t->st = s;
}

struct var * exists_glob(struct table * t, const char * nom) {
  struct var * ptr;
  for(ptr=t->glob ; ptr!=NULL ; ptr = ptr->prec){
    if(ptr->nom!=NULL) {
      if(strcmp(ptr->nom,nom)==0){
	return ptr;
      }
    }
  }
  return NULL;
}

struct var * exists_loc(struct table * t, const char * nom) {
  struct var * ptr;
  for(ptr=t->loc ; (ptr!=NULL)&&(ptr->type!=TYPE_FUNCTION); ptr = ptr->prec){
    if(ptr->nom!=NULL){
      if(strcmp(ptr->nom,nom)==0){
	return ptr;
      }
    }
  }
  return NULL;
}

void depiler (struct table * t){
  struct var * tmp = t->loc;
  t->loc = t->loc->prec;
  supprimer_var(tmp);
}

void depiler_bloc(struct table * t){
  do{
    depiler(t);
  }while(t->loc->type != TYPE_SENTINELLE);
}

void supprimer_table(struct table * t){
  struct var * ptr;
  struct sdc * ptr2;
  for(ptr=t->glob; ptr!=NULL ; ptr=ptr->prec) {
    supprimer_var(ptr);
  }
  for(ptr=t->loc; ptr!=NULL ; ptr=ptr->prec) {
    supprimer_var(ptr);
  }
  for(ptr=t->var_tmp; ptr!=NULL ; ptr=ptr->prec) {
    supprimer_var(ptr);
  }
  for(ptr2=t->st; ptr2!=NULL ; ptr2=ptr2->prec) {
    supprimer_sdc(ptr2);
  }
}

void supprimer_var(struct var * v){
  if(v->nom != NULL){
    free(v->nom);
  }
  if(v->type == TYPE_STRING){
    free(v->u.s);
  }
  if(v->code!=NULL){
    free(v->code);
  }
  free(v);
}

void supprimer_sdc(struct sdc * s) {
  free(s->code);
  free(s);
}

void concatener (char ** code, const char * tmp) {
  *code = (char *)realloc(*code,strlen(*code)+strlen(tmp)+1);
  strcat(*code,tmp);
}


struct exprs * init_exprs(void){
  struct exprs * res=(struct exprs *)malloc(sizeof(struct exprs));
  res->nb = 0;
  res->vars = NULL;
  return res;
}

void ajouter_expr(struct exprs * exprs, struct var * var){
  exprs->nb ++;
  exprs->vars = (struct var **)realloc(exprs->vars,exprs->nb*sizeof(struct var *));
  exprs->vars[exprs->nb-1] = var;
}

void supprimer_exprs (struct exprs * exprs){
  free(exprs->vars);
}

struct var * function_print (const char * name, const struct exprs * e, int * r){
  struct var * v=NULL;
  struct var * var_tmp=NULL;
  char * code = (char*)malloc(1);
  code[0]='\0';
  char tmp[MAX_LINE_SIZE];
  v = creer_var_void();
  sprintf(tmp,"call void @");
  concatener(&code,tmp);
  if(strcmp(name,"print_i")==0){
    if(e->nb != 1) {
      yyerror("Nombre de paramètres incorrect\n");
      exit(EXIT_FAILURE);
    }
    if(e->vars[0]->type != TYPE_INT) {
      yyerror("Type du paramètre incorrect\n");
      exit(EXIT_FAILURE);
    }
    sprintf(tmp,"print_i(i32 ");
    concatener(&code,tmp);
    if(e->vars[0]->calculable) {
      sprintf(tmp,"%d)\n",e->vars[0]->u.i);
    }
    else{
      if(e->vars[0]->nom != NULL){
	var_tmp = charger_var(e->vars[0],r);
	concatener(&(v->code),var_tmp->code);
	sprintf(tmp,"%%r%d)\n",var_tmp->r);
      }
      else {
	sprintf(tmp,"%%r%d)\n",e->vars[0]->r);
      }
    }
    concatener(&code,tmp);
  }
  else if(strcmp(name,"print_f")==0){
    if(e->nb != 1) {
      yyerror("Nombre de paramètres incorrect\n");
      exit(EXIT_FAILURE);
    }
    if(e->vars[0]->type != TYPE_FLOAT) {
      yyerror("Type du paramètre incorrect\n");
      exit(EXIT_FAILURE);
    }
    sprintf(tmp,"print_f(float ");
    concatener(&code,tmp);
    if(e->vars[0]->calculable) {
      sprintf(tmp,"%e)\n",e->vars[0]->u.f);
    }
    else{
      if(e->vars[0]->nom != NULL){
	var_tmp = charger_var(e->vars[0],r);
	concatener(&(v->code),var_tmp->code);
	sprintf(tmp,"%%r%d)\n",var_tmp->r);
      }
      else {
	sprintf(tmp,"%%r%d)\n",e->vars[0]->r);
      }
    }
    concatener(&code,tmp);
  }
  concatener(&(v->code),code);
  return v;
}
